﻿///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation. All rights reserved.
///////////////////////////////////////////////////////////////////////////////
//
// Type Library Importer utility
//
// This program imports all the types in the type library into a interop assembly
//
///////////////////////////////////////////////////////////////////////////////

namespace tlbimp2.Event
{
    using System.Runtime.InteropServices;
    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Collections;
    using System.Diagnostics;

    internal class EventSinkHelperWriter
    {
        public static readonly String GeneratedTypeNamePostfix = "_SinkHelper";

        public EventSinkHelperWriter(ModuleBuilder OutputModule, Type InputType, Type EventItfType)
        {
            m_InputType = InputType;
            m_OutputModule = OutputModule;
            m_EventItfType = EventItfType;
        }

        public Type Perform()
        {
            // Create the output Type.
            Type[] aInterfaces = new Type[1];
            aInterfaces[0] = m_InputType;
            String strFullName = null;
            String strNameSpace = NameSpaceExtractor.ExtractNameSpace(m_EventItfType.FullName);

            if (strNameSpace != "")
                strFullName = strNameSpace + ".";

            strFullName += m_InputType.Name + GeneratedTypeNamePostfix;
            TypeBuilder OutputTypeBuilder = TCEAdapterGenerator.DefineUniqueType(
                                                             strFullName,
                                                             TypeAttributes.Sealed | TypeAttributes.Public,
                                                             null,
                                                             aInterfaces,
                                                             m_OutputModule
                                                            );
            // Hide the _SinkProvider interface
            TCEAdapterGenerator.SetHiddenAttribute(OutputTypeBuilder);

            // Set the class interface to none.
            TCEAdapterGenerator.SetClassInterfaceTypeToNone(OutputTypeBuilder);

            // Retrieve the property methods on the input interface and give them a dummy implementation.
            MethodInfo[] pMethods = TCEAdapterGenerator.GetPropertyMethods(m_InputType);
            foreach (MethodInfo method in pMethods)
            {
                DefineBlankMethod(OutputTypeBuilder, method);
            }

            // Retrieve the non-property methods on the input interface.
            MethodInfo[] aMethods = TCEAdapterGenerator.GetNonPropertyMethods(m_InputType);

            // Allocate an array to contain the delegate fields.
            FieldBuilder[] afbDelegates = new FieldBuilder[aMethods.Length];
            // Process all the methods on the input interface.
            for (int cMethods = 0; cMethods < aMethods.Length; cMethods++)
            {
                if (m_InputType == aMethods[cMethods].DeclaringType)
                {
                    // Retrieve the delegate type from the add_XXX method.
                    MethodInfo AddMeth = m_EventItfType.GetMethod("add_" + aMethods[cMethods].Name);
                    ParameterInfo[] aParams = AddMeth.GetParameters();
                    Debug.Assert(aParams.Length == 1, "All event interface methods must take a single delegate derived type and have a void return type");
                    Type DelegateCls = aParams[0].ParameterType;

                    // Define the delegate instance field.
                    afbDelegates[cMethods] = OutputTypeBuilder.DefineField(
                                                  "m_" + aMethods[cMethods].Name + "Delegate",
                                                  DelegateCls,
                                                  FieldAttributes.Public
                                                 );

                    // Define the event method itself.
                    DefineEventMethod(OutputTypeBuilder, aMethods[cMethods], DelegateCls, afbDelegates[cMethods]);
                }
            }

            // Create the cookie field.
            FieldBuilder fbCookie = OutputTypeBuilder.DefineField(
                                                  "m_dwCookie",
                                                  typeof(Int32),
                                                  FieldAttributes.Public
                                                 );

            // Define the constructor.      
            DefineConstructor(OutputTypeBuilder, fbCookie, afbDelegates);

            return OutputTypeBuilder.CreateType();
        }

        private void DefineBlankMethod(TypeBuilder OutputTypeBuilder, MethodInfo Method)
        {
            ParameterInfo[] PIs = Method.GetParameters();
            Type[] parameters = new Type[PIs.Length];
            for (int i = 0; i < PIs.Length; i++)
            {
                parameters[i] = PIs[i].ParameterType;
            }

            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(Method.Name,
                                                                Method.Attributes & ~MethodAttributes.Abstract,
                                                                Method.CallingConvention,
                                                                Method.ReturnType,
                                                                parameters);

            ILGenerator il = Meth.GetILGenerator();

            AddReturn(Method.ReturnType, il, Meth);

            il.Emit(OpCodes.Ret);
        }

        private void DefineEventMethod(TypeBuilder OutputTypeBuilder, MethodInfo Method, Type DelegateCls, FieldBuilder fbDelegate)
        {
            // Retrieve the method info for the invoke method on the delegate.
            MethodInfo DelegateInvokeMethod = DelegateCls.GetMethod("Invoke");
            Debug.Assert(DelegateInvokeMethod != null, "Unable to find method Delegate.Invoke()");

            // Retrieve the return type.
            Type ReturnType = Method.ReturnType;

            // Define the actual event method.
            ParameterInfo[] paramInfos = Method.GetParameters();
            Type[] parameterTypes;
            if (paramInfos != null)
            {
                parameterTypes = new Type[paramInfos.Length];
                for (int i = 0; i < paramInfos.Length; i++)
                {
                    parameterTypes[i] = paramInfos[i].ParameterType;
                }
            }
            else
                parameterTypes = null;

            MethodAttributes attr = MethodAttributes.Public | MethodAttributes.Virtual;
            MethodBuilder Meth = OutputTypeBuilder.DefineMethod(Method.Name,
                                                                 attr,
                                                                 CallingConventions.Standard,
                                                                 ReturnType,
                                                                 parameterTypes);

            // We explicitly do not specify parameter name and attributes since this Type
            // is not meant to be exposed to the user. It is only used internally to do the
            // connection point to TCE mapping.

            ILGenerator il = Meth.GetILGenerator();

            // Create the exit branch.
            Label ExitLabel = il.DefineLabel();

            // Generate the code that verifies that the delegate is not null.
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbDelegate);
            il.Emit(OpCodes.Brfalse, ExitLabel);

            // The delegate is not NULL so we need to invoke it.
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldfld, fbDelegate);

            // Generate the code to load the arguments before we call invoke.
            ParameterInfo[] aParams = Method.GetParameters();
            for (int cParams = 0; cParams < aParams.Length; cParams++)
            {
                il.Emit(OpCodes.Ldarg, (short)(cParams + 1));
            }

            // Generate a tail call to invoke. This will cause the callvirt to return 
            // directly to the caller of the current method instead of actually coming
            // back to the current method and returning. This will cause the value returned
            // from the call to the COM server to be returned to the caller of this method.

            il.Emit(OpCodes.Callvirt, DelegateInvokeMethod);
            il.Emit(OpCodes.Ret);

            // This is the label that will be jumped to if no delegate is present.
            il.MarkLabel(ExitLabel);

            AddReturn(ReturnType, il, Meth);

            il.Emit(OpCodes.Ret);

        }

        private void AddReturn(Type ReturnType, ILGenerator il, MethodBuilder Meth)
        {
            // Place a dummy return value on the stack before we return.
            if (ReturnType == typeof(void))
            {
                // There is nothing to place on the stack.
            }
            else if (ReturnType.IsPrimitive)
            {
                switch (System.Type.GetTypeCode(ReturnType))
                {
                    case TypeCode.Boolean:
                    case TypeCode.Char:
                    case TypeCode.Byte:
                    case TypeCode.SByte:
                    case TypeCode.Int16:
                    case TypeCode.UInt16:
                    case TypeCode.Int32:
                    case TypeCode.UInt32:
                        il.Emit(OpCodes.Ldc_I4_0);
                        break;

                    case TypeCode.Int64:
                    case TypeCode.UInt64:
                        il.Emit(OpCodes.Ldc_I4_0);
                        il.Emit(OpCodes.Conv_I8);
                        break;

                    case TypeCode.Single:
                        il.Emit(OpCodes.Ldc_R4, 0);
                        break;

                    case TypeCode.Double:
                        il.Emit(OpCodes.Ldc_R4, 0);
                        il.Emit(OpCodes.Conv_R8);
                        break;

                    default:
                        // "TypeCode" does not include IntPtr, so special case it.
                        if (ReturnType == typeof(IntPtr))
                            il.Emit(OpCodes.Ldc_I4_0);
                        else
                            Debug.Assert(false, "Unexpected type for Primitive type.");
                        break;
                }
            }
            else if (ReturnType.IsValueType)
            {
                // Allocate stack space for the return value type.  Zero-init.
                Meth.InitLocals = true;
                LocalBuilder ltRetVal = il.DeclareLocal(ReturnType);

                // Load the value class on the stack.
                il.Emit(OpCodes.Ldloc_S, ltRetVal);

            }
            else
            {
                // The return type is a normal type.
                il.Emit(OpCodes.Ldnull);
            }
        }

        private void DefineConstructor(TypeBuilder OutputTypeBuilder, FieldBuilder fbCookie, FieldBuilder[] afbDelegates)
        {
            // Retrieve the constructor info for the base classe's constructor.
            ConstructorInfo DefaultBaseClsCons = typeof(Object).GetConstructor(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public, null, new Type[0], null);
            Debug.Assert(DefaultBaseClsCons != null, "Unable to find the constructor for class " + m_InputType.Name);

            // Define the default constructor.
            MethodBuilder Cons = OutputTypeBuilder.DefineMethod(".ctor",
                                                                 MethodAttributes.Assembly | MethodAttributes.SpecialName,
                                                                 CallingConventions.Standard,
                                                                 null,
                                                                 null);

            ILGenerator il = Cons.GetILGenerator();

            // Generate the code to call the constructor of the base class.
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Call, DefaultBaseClsCons);

            // Generate the code to set the cookie field to 0.
            il.Emit(OpCodes.Ldarg, (short)0);
            il.Emit(OpCodes.Ldc_I4, 0);
            il.Emit(OpCodes.Stfld, fbCookie);

            // Generate the code to set all the delegates to NULL.
            for (int cDelegates = 0; cDelegates < afbDelegates.Length; cDelegates++)
            {
                if (afbDelegates[cDelegates] != null)
                {
                    il.Emit(OpCodes.Ldarg, (short)0);
                    il.Emit(OpCodes.Ldnull);
                    il.Emit(OpCodes.Stfld, afbDelegates[cDelegates]);
                }
            }

            // Emit the return opcode.
            il.Emit(OpCodes.Ret);

        }

        private Type m_InputType;
        private Type m_EventItfType;
        private ModuleBuilder m_OutputModule;
    }
}
